package ru.inse.core.common;

import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;


public class Utils
{
    private static final String SLASH = "/";

    private static final Pattern SLASH_PATTERN = Pattern.compile(SLASH);

    private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");

    private static final Pattern COMMA_PATTERN = Pattern.compile("\\s*,\\s*");

    private static final int BUFFER_SIZE = 5000;

//    /**
//     * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
//     * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
//     * following word), thus "user_id" also becomes "User Id".
//     */
//    public static String toUserPresentable(String id)
//    {
//        StringBuilder builder = new StringBuilder(id.length() * 2);
//
//        char[] chars = id.toCharArray();
//        boolean postSpace = true;
//        boolean upcaseNext = true;
//
//        for (char ch : chars)
//        {
//            if (upcaseNext)
//            {
//                builder.append(Character.toUpperCase(ch));
//                upcaseNext = false;
//
//                continue;
//            }
//
//            if (ch == '_')
//            {
//                builder.append(' ');
//                upcaseNext = true;
//                continue;
//            }
//
//            boolean upperCase = Character.isUpperCase(ch);
//
//            if (upperCase && !postSpace)
//                builder.append(' ');
//
//            builder.append(ch);
//
//            postSpace = upperCase;
//        }
//
//        return builder.toString();
//    }
//
//    public static Map<String, String> mapFromKeysAndValues(String... keysAndValues)
//    {
//        Map<String, String> result = CollectionFactory.newMap();
//
//        int i = 0;
//        while (i < keysAndValues.length)
//        {
//            String key = keysAndValues[i++];
//            String value = keysAndValues[i++];
//
//            result.put(key, value);
//        }
//
//        return result;
//    }
//
//    /**
//     * Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the equals sign is
//     * omitted, then the same value is used for both value and label.
//     */
//    public static OptionModel toOptionModel(String input)
//    {
//        assert input != null;
//        int equalsx = input.indexOf('=');
//
//        if (equalsx < 0)
//            return new OptionModelImpl(input);
//
//        String value = input.substring(0, equalsx);
//        String label = input.substring(equalsx + 1);
//
//        return new OptionModelImpl(label, value);
//    }
//
//    /**
//     * Parses a string input into a series of value=label pairs compatible with {@link #toOptionModel(String)}. Splits
//     * on commas. Ignores whitespace around commas.
//     *
//     * @param input comma seperated list of terms
//     * @return list of option models
//     */
//    public static List<OptionModel> toOptionModels(String input)
//    {
//        assert input != null;
//        List<OptionModel> result = CollectionFactory.newList();
//
//        for (String term : input.split(","))
//            result.add(toOptionModel(term.trim()));
//
//        return result;
//    }
//
//    /**
//     * Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option groups).
//     */
//    public static SelectModel toSelectModel(String input)
//    {
//        List<OptionModel> options = toOptionModels(input);
//
//        return new SelectModelImpl(null, options);
//    }
//
//    /**
//     * Converts a map entry to an {@link OptionModel}.
//     */
//    public static OptionModel toOptionModel(Map.Entry input)
//    {
//        assert input != null;
//        String label = input.getValue() != null ? String.valueOf(input.getValue()) : "";
//
//        return new OptionModelImpl(label, input.getKey());
//    }
//
//    /**
//     * Processes a map input into a series of map entries compatible with {@link #toOptionModel(Map.Entry)}.
//     *
//     * @param input map of elements
//     * @return list of option models
//     */
//    public static <K, V> List<OptionModel> toOptionModels(Map<K, V> input)
//    {
//        assert input != null;
//        List<OptionModel> result = CollectionFactory.newList();
//
//        for (Map.Entry entry : input.entrySet())
//            result.add(toOptionModel(entry));
//
//        return result;
//    }
//
//    /**
//     * Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option groups).
//     */
//    public static <K, V> SelectModel toSelectModel(Map<K, V> input)
//    {
//        List<OptionModel> options = toOptionModels(input);
//
//        return new SelectModelImpl(null, options);
//    }
//
//    /**
//     * Converts an object to an {@link OptionModel}.
//     */
//    public static OptionModel toOptionModel(Object input)
//    {
//        String label = (input != null ? String.valueOf(input) : "");
//
//        return new OptionModelImpl(label, input);
//    }
//
//    /**
//     * Processes a list input into a series of objects compatible with {@link #toOptionModel(Object)}.
//     *
//     * @param input list of elements
//     * @return list of option models
//     */
//    public static <E> List<OptionModel> toOptionModels(List<E> input)
//    {
//        assert input != null;
//        List<OptionModel> result = CollectionFactory.newList();
//
//        for (E element : input)
//            result.add(toOptionModel(element));
//
//        return result;
//    }
//
//    /**
//     * Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option groups).
//     */
//    public static <E> SelectModel toSelectModel(List<E> input)
//    {
//        List<OptionModel> options = toOptionModels(input);
//
//        return new SelectModelImpl(null, options);
//    }
//
//    /**
//     * Parses a key/value pair where the key and the value are seperated by an equals sign. The key and value are
//     * trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}.
//     */
//    public static KeyValue parseKeyValue(String input)
//    {
//        int pos = input.indexOf('=');
//
//        if (pos < 1)
//            throw new IllegalArgumentException(InternalMessages.badKeyValue(input));
//
//        String key = input.substring(0, pos);
//        String value = input.substring(pos + 1);
//
//        return new KeyValue(key.trim(), value.trim());
//    }
//
//    /**
//     * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
//     * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
//     * underscore).
//     *
//     * @param expression a property expression
//     * @return the expression with punctuation removed
//     */
//    public static String extractIdFromPropertyExpression(String expression)
//    {
//        return replace(expression, NON_WORD_PATTERN, "");
//    }
//
//    /**
//     * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
//     * user presentable form.
//     */
//    public static String defaultLabel(String id, Messages messages, String propertyExpression)
//    {
//        String key = id + "-label";
//
//        if (messages.contains(key))
//            return messages.get(key);
//
//        return toUserPresentable(extractIdFromPropertyExpression(lastTerm(propertyExpression)));
//    }
//
//    /**
//     * Strips a dotted sequence (such as a property expression, or a qualified class name) down to the last term of that
//     * expression, by locating the last period ('.') in the string.
//     */
//    public static String lastTerm(String input)
//    {
//        int dotx = input.lastIndexOf('.');
//
//        return input.substring(dotx + 1);
//    }
//
//    /**
//     * Converts an list of strings into a space-separated string combining them all, suitable for use as an HTML class
//     * attribute value.
//     *
//     * @param classes classes to combine
//     * @return the joined classes, or null if classes is empty
//     */
//    public static String toClassAttributeValue(List<String> classes)
//    {
//        if (classes.isEmpty())
//            return null;
//
//        return InternalUtils.join(classes, " ");
//    }
//
//    /**
//     * Converts an enum to a label string, allowing for overrides from a message catalog.
//     * <p/>
//     * <ul>
//     * <li>As key <em>prefix</em>.<em>name</em> if present. Ex: "ElementType.LOCAL_VARIABLE"
//     * <li>As key <em>name</em> if present, i.e., "LOCAL_VARIABLE".
//     * <li>As a user-presentable version of the name, i.e., "Local Variable".
//     * </ul>
//     *
//     * @param messages the messages to search for the label
//     * @param prefix   prepended to key
//     * @param value    to get a label for
//     * @return the label
//     */
//    public static String getLabelForEnum(Messages messages, String prefix, Enum value)
//    {
//        String name = value.name();
//
//        String key = prefix + "." + name;
//
//        if (messages.contains(key))
//            return messages.get(key);
//
//        if (messages.contains(name))
//            return messages.get(name);
//
//        return toUserPresentable(name.toLowerCase());
//    }
//
//    public static String getLabelForEnum(Messages messages, Enum value)
//    {
//        String prefix = lastTerm(value.getClass().getName());
//
//        return getLabelForEnum(messages, prefix, value);
//    }
//
//    private static String replace(String input, Pattern pattern, String replacement)
//    {
//        return pattern.matcher(input).replaceAll(replacement);
//    }
//
//    /**
//     * Determines if the two values are equal. They are equal if they are the exact same value (including if they are
//     * both null). Otherwise standard equals() comparison is used.
//     *
//     * @param left  value to compare, possibly null
//     * @param right value to compare, possibly null
//     * @return true if same value, both null, or equal
//     */
    public static <T> boolean isEqual(T left, T right)
    {
        if (left == right)
            return true;

        if (left == null)
            return false;

        return left.equals(right);
    }

    /**
     * Splits a path at each slash.
     */
    public static String[] splitPath(String path)
    {
        return SLASH_PATTERN.split(path);
    }

//    /**
//     * Splits a value around commas. Whitespace around the commas is removed, as is leading and trailing whitespace.
//     *
//     * @since 5.1.0.0
//     */
//    public static String[] splitAtCommas(String value)
//    {
//        if (InternalUtils.isBlank(value))
//            return InternalConstants.EMPTY_STRING_ARRAY;
//
//        return COMMA_PATTERN.split(value.trim());
//    }
//
//    /**
//     * Copies some content from an input stream to an output stream. It is the caller's responsibility to close the
//     * streams.
//     *
//     * @param in  source of data
//     * @param out sink of data
//     * @throws IOException
//     * @since 5.1.0.0
//     */
//    public static void copy(InputStream in, OutputStream out) throws IOException
//    {
//        byte[] buffer = new byte[BUFFER_SIZE];
//
//        while (true)
//        {
//            int length = in.read(buffer);
//
//            if (length < 0)
//                break;
//
//            out.write(buffer, 0, length);
//        }
//
//        // TAPESTRY-2415: WebLogic needs this flush() call.
//        out.flush();
//    }
//
//    public static boolean isEqual(EventContext left, EventContext right)
//    {
//        if (left == right)
//            return true;
//
//        int count = left.getCount();
//
//        if (count != right.getCount())
//            return false;
//
//        for (int i = 0; i < count; i++)
//        {
//            if (!left.get(Object.class, i).equals(right.get(Object.class, i)))
//                return false;
//        }
//
//        return true;
//    }
//
//    /**
//     * Converts an Asset to an Asset2 if necessary. When actually wrapping an Asset as an Asset2, the asset is assumed
//     * to be variant (i.e., not cacheable).
//     *
//     * @since 5.1.0.0
//     */
//    public static Asset2 toAsset2(final Asset asset)
//    {
//        if (asset instanceof Asset2)
//            return (Asset2) asset;
//
//        return new Asset2()
//        {
//            /** Returns false. */
//            public boolean isInvariant()
//            {
//                return false;
//            }
//
//            public Resource getResource()
//            {
//                return asset.getResource();
//            }
//
//            public String toClientURL()
//            {
//                return asset.toClientURL();
//            }
//
//            @Override
//            public String toString()
//            {
//                return asset.toString();
//            }
//        };
//    }
//
//    public static InternalPropertyConduit toInternalPropertyConduit(final PropertyConduit conduit)
//    {
//        if (conduit instanceof InternalPropertyConduit)
//            return (InternalPropertyConduit) conduit;
//
//        return new InternalPropertyConduit()
//        {
//
//            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
//            {
//                return conduit.getAnnotation(annotationClass);
//            }
//
//            public void set(Object instance, Object value)
//            {
//                conduit.set(instance, value);
//            }
//
//            public Class getPropertyType()
//            {
//                return conduit.getPropertyType();
//            }
//
//            public Object get(Object instance)
//            {
//                return conduit.get(instance);
//            }
//
//            public String getPropertyName()
//            {
//                return null;
//            }
//        };
//    }
//
//    /**
//     * @param mixinDef the original mixin definition.
//     * @return an Orderable whose id is the mixin name.
//     */
//    public static Orderable<String> mixinTypeAndOrder(String mixinDef)
//    {
//        int idx = mixinDef.indexOf("::");
//        if (idx == -1)
//        {
//            return new Orderable<String>(mixinDef, mixinDef);
//        }
//        String type = mixinDef.substring(0, idx);
//        String[] constraints = splitMixinConstraints(mixinDef.substring(idx + 2));
//
//        return new Orderable<String>(type, type, constraints);
//    }
//
//    public static String[] splitMixinConstraints(String s)
//    {
//        return InternalUtils.isBlank(s) ? null : s.split(";");
//    }
//
//    /**
//     * Common mapper, used primarily with {@link org.apache.tapestry5.func.Flow#map(org.apache.tapestry5.func.Mapper)}
//     *
//     * @since 5.2.0
//     */
//    public static Mapper<Asset, StylesheetLink> assetToStylesheetLink = new Mapper<Asset, StylesheetLink>()
//    {
//        public StylesheetLink map(Asset input)
//        {
//            return new StylesheetLink(input);
//        }
//    };
//
//    public static LinkCreationListener2 toLinkCreationListener2(final LinkCreationListener delegate)
//    {
//        return new LinkCreationListener2()
//        {
//
//            public void createdPageRenderLink(Link link, PageRenderRequestParameters parameters)
//            {
//                delegate.createdPageRenderLink(link);
//            }
//
//            public void createdComponentEventLink(Link link, ComponentEventRequestParameters parameters)
//            {
//                delegate.createdComponentEventLink(link);
//            }
//        };
//    }
//
//    /**
//     * @since 5.3
//     */
//    public static String toFileSuffix(String fileName)
//    {
//        int dotx = fileName.lastIndexOf('.');
//
//        return dotx < 0 ? "" : fileName.substring(dotx + 1);
//    }
//
//    /**
//     * Performs an operation and re-throws the IOException that may occur.
//     */
//    public static void performIO(OperationTracker tracker, String description, final IOOperation operation)
//            throws IOException
//    {
//        final Holder<IOException> exceptionHolder = Holder.create();
//
//        tracker.run(description, new Runnable()
//        {
//            public void run()
//            {
//                try
//                {
//                    operation.perform();
//                } catch (IOException ex)
//                {
//                    exceptionHolder.put(ex);
//                }
//            }
//        });
//
//        if (exceptionHolder.hasValue())
//            throw exceptionHolder.get();
//    }

    /**
     * Extracts a value from a  map of references. Handles the case where the reference does not exist,
     * and the case where the reference itself now contains null.
     *
     * @since 5.3
     */
    public static <K, V> V getAndDeref(Map<K, ? extends Reference<V>> map, K key)
    {
        Reference<V> ref = map.get(key);

        return ref == null ? null : ref.get();
    }


    /**
     * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace).
     */

    public static boolean isEmpty(String input)
    {
        return input == null || input.length() == 0 || input.trim().length() == 0;
    }

    /**
     * Returns true if the input is an empty collection.
     */

    public static boolean isEmptyCollection(Object input)
    {
        if (input instanceof Collection)
        {
            return ((Collection) input).isEmpty();
        }

        return false;
    }



    /**
     * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings.
     *
     * @param map the map to extract keys from (may be null)
     * @return the sorted keys, or the empty set if map is null
     */

    public static List<String> sortedKeys(Map map)
    {
        if (map == null)
            return Collections.emptyList();

        List<String> keys = new ArrayList<String>();

        for (Object o : map.keySet())
            keys.add(String.valueOf(o));

        Collections.sort(keys);

        return keys;
    }

    public static <K, V> Set<K> keys(Map<K, V> map)
    {
        if (map == null)
            return Collections.emptySet();

        return map.keySet();
    }

    /**
     * Converts an enumeration (of Strings) into a sorted list of Strings.
     */
    public static List<String> toList(Enumeration e)
    {
        List<String> result = new ArrayList<String>();

        while (e.hasMoreElements())
        {
            String name = (String) e.nextElement();

            result.add(name);
        }

        Collections.sort(result);

        return result;
    }

    public static <K, V> V get(Map<K, V> map, K key)
    {
        if (map == null)
            return null;

        return map.get(key);
    }

}

